package pile.pay.service.impl;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.shardingsphere.api.hint.HintManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import pile.pay.config.wxConfig.*;
import pile.pay.entity.*;
import pile.pay.service.*;
import pile.pay.utils.EmailUtil;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.util.*;

/**
 * 微信支付服务类
 */
@Service
@Slf4j
public class WxPayServiceImpl implements IWxPayService {

    @Resource
    private WechatPayConfig wechatPayConfig;
    @Resource
    private WechatPayRequest wechatPayRequest;

    @Resource
    private Verifier verifier;


    @Autowired
    private EmailUtil emailUtil;

    @Autowired
    private IRechargeService recechargeService;

    @Autowired
    private IMemberSubsidiaryService memberSubsidiaryService;

    @Autowired
    private IRechargeService rechargeService;

    @Autowired
    private IRefundOrderService refundOrderService;

    @Autowired
    private IBalanceChangeService balanceChangeService;


    @Override
    @Transactional
    public ResponseResult transactions(WxPayEntity wxPayEntity) throws IOException, NoSuchAlgorithmException, SignatureException, InvalidKeyException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode rootNode = objectMapper.createObjectNode();
        String mchid = "";
        //根据我们自己生成的 订单号 查询充值 信息 加锁  统一下单 支付回调调用
        Recharge recharge = recechargeService.selectByRechargeOrderNoLock(wxPayEntity.getOutTradeNo());

        String openId = "";
        //如果前端传值，就用前端。前端不传就用后端的
        if (StringUtils.isNotBlank(wxPayEntity.getOpenId())) {
            openId = wxPayEntity.getOpenId();
        } else {
            //需要前段多传一个渠道类型了  因为卫莱电 和卫凡电都有可能会调用
            //fengin调用的时候再bussiness 要查询
            MemberSubsidiary memberSubsidiary = memberSubsidiaryService.selectOpenidByType(wxPayEntity.getChannel(), wxPayEntity.getMemberId());
            openId = memberSubsidiary.getOpenid();
        }
        rootNode.put("sp_appid", wechatPayConfig.getAppId())
                .put("sp_mchid", wechatPayConfig.getMchId())
                .put("notify_url", wechatPayConfig.getNotifyUrl())
                .put("sub_mchid", mchid)
                .put("description", wxPayEntity.getDescription())
                .put("attach", wxPayEntity.getAttach())
                .put("out_trade_no", wxPayEntity.getOutTradeNo() + "");
        rootNode.putObject("amount")
                .put("total", (new BigDecimal(String.valueOf(wxPayEntity.getTotal())).multiply(new BigDecimal(100))).intValue())
                .put("currency", "CNY");
        rootNode.putObject("payer")
                .put("sp_openid", openId);
        // 是否分账 根据场景来判断，是否使用数组
        if ("1".equals(wxPayEntity.getAttach())) {
            rootNode.putObject("settle_info")
                    .put("profit_sharing", true);
        }
        objectMapper.writeValue(bos, rootNode);
        String paramsStr = bos.toString("UTF-8");
        log.info("请求参数 ===> {}" + paramsStr);
        Map<String, String> signMap = new HashMap<>();
        try {
            String resStr = wechatPayRequest.wechatHttpPost(wechatPayConfig.getBaseUrl().concat(WechatPayUrlEnum.PAY_TRANSACTIONS.getType().concat("jsapi")), paramsStr);
            Map<String, Object> resMap = JSONObject.parseObject(resStr, new TypeReference<Map<String, Object>>() {
            });
            signMap = paySignMsg(resMap, "jsapi");

        } catch (RuntimeException e) {
            log.error("微信统一下单报错" + e.getMessage());
            String[] messages = e.getMessage().split("message\":\"");
            if (messages.length == 1) {
                return new ResponseResult(203, "支付失败,请稍后再试");
            }
            String message = messages[1];
            String ledgerFailureReason = message.substring(0, message.length() - 2);

            //发送邮件
            try {
                String to = "709286540@qq.com";
                String title = "微信统一下单失败,商户号:" + recharge.getMchId();
                String content = JSON.toJSONString(recharge);
                emailUtil.sendMessage(to, title, content);
            } catch (Exception e1) {
                log.error("微信统一下单失败发送邮件失败" + e.getMessage());
            }

            return new ResponseResult(203, ledgerFailureReason);
        }
        return new ResponseResult(signMap);

    }

    /**
     * 支付回调
     *
     * @param request
     * @param response
     * @return
     */
    @Transactional
    @Override
    public Map<String, String> payNotify(HttpServletRequest request, HttpServletResponse response) {

        // 处理通知参数
        Map<String, Object> bodyMap = getNotifyBody(request);
        if (bodyMap == null) {
            return falseMsg(response);
        }
        //强制读主库
        HintManager.getInstance().setMasterRouteOnly();

        try {
            // 解密resource中的通知数据
            String resource = bodyMap.get("resource").toString();
            Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 1);

            // 结果正确 充值订单号
            String orderId = resourceMap.get("out_trade_no").toString();
            //微信支付单号
            String tradeNo = resourceMap.get("transaction_id").toString();
            //实际支付金额 单位元
            String totalFee = ((Map) resourceMap.get("amount")).get("total").toString();
            //获取场景
            //支付成功
            if (!"SUCCESS".equals(resourceMap.get("trade_state"))) {
                return trueMsg(response);
            }
            Recharge recharge = rechargeService.selectByRechargeOrderNoLock(orderId);
            if (recharge == null) {
                log.error("微信原生支付回调，支付充值订单不存在，回调参数:" + resourceMap);
                return trueMsg(response);
            }

            if (!(new BigDecimal(totalFee).compareTo(recharge.getRechargeAmount()) == 0)) {
                //金额不一致 不管
                log.error("微信原生支付回调，充值表金额与支付回调金额不一致，回调参数:" + resourceMap);
                return trueMsg(response);
            }
            if (recharge.getPaymentStatus() != 0) {
                //状态不是未支付
                log.error("微信原生支付回调，充值表金额状态不是未支付:" + "微信支付单号:" + tradeNo);
                return trueMsg(response);
            }
            //第三方订单号
            recharge.setThirdOrderNumber(tradeNo);
            //支付渠道
            recharge.setPaymentChannel(1);
            //充值回调的逻辑 有三个支付 写一个公共的方法
            rechargeService.rechargeCallbackProcessLogic(recharge);

        } finally {
            //释放设置
            HintManager.clear();
        }
        //成功应答
        return trueMsg(response);
    }


    /**
     * 申请退款
     *
     * @param
     * @return
     * @throws IOException
     */
    @Override
    public ResponseResult refunds()  {
        //默认传1 是微信退款
        List<Recharge> recharges = recechargeService.selectByRemainingAmountByPaymentChannel(1);

        WxRefundEntity wxRefundEntity = new WxRefundEntity();
        RefundOrder refundOrder = new RefundOrder();

        if (recharges.size() == 0) {
            return new ResponseResult(200, true);
        }

        for (Recharge recharge : recharges) {
            if (recharge.getRemainingAmount().compareTo(new BigDecimal(0)) == 0) {
                recharge.setRefundStatus(1);
                recechargeService.updateById(recharge);
                continue;
            }
            String replaceUUID = UUID.randomUUID().toString().replace("-", "");
            //子商户号
            wxRefundEntity.setSubMchid(String.valueOf(recharge.getMchId()));
            //微信支付订单号
            wxRefundEntity.setTransactionId(recharge.getThirdOrderNumber());
            //商户退款订单号
            wxRefundEntity.setOutRefundNo(replaceUUID);
            refundOrder.setOutRefundNo(replaceUUID);
            //退款理由
            wxRefundEntity.setReason("充电完成退款");
            //退款金额
            wxRefundEntity.setRefund(recharge.getRemainingAmount().floatValue());
            //原支付金额
            wxRefundEntity.setTotal(recharge.getRechargeAmount().floatValue());
            //附加数据
            wxRefundEntity.setAttach("1");
            //微信支付订单号
            refundOrder.setTransactionId(recharge.getThirdOrderNumber());
            //商户退款单号
            refundOrder.setOutRefundNo(replaceUUID);
            //电站id
            refundOrder.setStationId(recharge.getStationId());
            refundOrderService.add(refundOrder);

            //微信原生退款方法

        }

        return new ResponseResult(200, false);
    }

    /**
     * 退款回调
     *
     * @param request
     * @param response
     * @return
     */
    @Transactional
    @Override
    public Map<String, String> refundNotify(HttpServletRequest request, HttpServletResponse response) {
        log.info("退款回调");
        // 处理通知参数
        Map<String, Object> bodyMap = getNotifyBody(request);
        if (bodyMap == null) {
            return falseMsg(response);
        }
        log.info("=========== 在对业务数据进行状态检查和处理之前，要采用数据锁进行并发控制，以避免函数重入造成的数据混乱 ===========");
        try {
            // 解密resource中的通知数据
            String resource = bodyMap.get("resource").toString();
            Map<String, Object> resourceMap = WechatPayValidator.decryptFromResource(resource, wechatPayConfig.getApiV3Key(), 2);
            log.info("通知参数：{}", JSON.toJSONString(resourceMap));
            String orderNo = resourceMap.get("out_trade_no").toString();
            String transactionId = resourceMap.get("transaction_id").toString();
            //获取退款状态
            String status = resourceMap.get("refund_status").toString();
            // TODO 根据订单号，做幂等处理，并且在对业务数据进行状态检查和处理之前，要采用数据锁进行并发控制，以避免函数重入造成的数据混乱
            log.info("=========== 根据订单号，做幂等处理 ===========");
            log.info(JSON.toJSONString(resourceMap));
            //todo 充值记录表中的剩余金额修改为0 修改退款状态

            Recharge recharge = rechargeService.selectByRechargeOrderNoLock(orderNo);
            //根据第三方未微信支付订单号查询
            RefundOrder refundOrder = refundOrderService.selectByTransactionId(transactionId);
            if (recharge == null) {
                //成功应答
                return trueMsg(response);
            }
            if ("SUCCESS".equals(status)) {
                //退款成功
                recharge.setRefundStatus(1);
                //修改剩余余额
                //退款成功 生成退款记录
                BalanceChange balanceChange = new BalanceChange();

                //剩余金额就是 本次的退款金额
                BigDecimal remainingAmount = recharge.getRemainingAmount();

                //金额
                balanceChange.setAmount(remainingAmount);

                recharge.setRemainingAmount(new BigDecimal(0));
                refundOrder.setStatus("SUCCESS");
                refundOrderService.updateById(refundOrder);
                rechargeService.updateById(recharge);

                //用户id
                balanceChange.setMemberId(recharge.getMemberId());
                //类型 收入
                balanceChange.setType(1);
                //备注
                balanceChange.setRemarks("余额退款");

                //变动之前
                balanceChange.setBalanceBefore(new BigDecimal(0));
                //变动之后
                balanceChange.setBalanceAfter(new BigDecimal(0));
                //场景 退款成功 7
                balanceChange.setScene(7);
                //渠道
                balanceChange.setChannel(recharge.getChannel());
                balanceChangeService.add(balanceChange);

                //发送订阅消息

                //sendSubscriptionMessage( recharge,remainingAmount);
            } else {
                //失败  则把状态修改为为退款重新来过
                recharge.setRefundStatus(0);
                rechargeService.updateById(recharge);
                refundOrder.setStatus("失败");
                refundOrderService.updateById(refundOrder);
            }

        } finally {

        }
        //成功应答
        return trueMsg(response);

    }


    private Map<String, String> paySignMsg(Map<String, Object> map, String type) throws UnsupportedEncodingException, NoSuchAlgorithmException, SignatureException, InvalidKeyException {
        long timeMillis = System.currentTimeMillis();
        String appId = wechatPayConfig.getAppId();
        String timeStamp = timeMillis / 1000 + "";
        String nonceStr = timeMillis + "";
        String prepayId = map.get("prepay_id").toString();
        String packageStr = "prepay_id=" + prepayId;
        // 公共参数
        Map<String, String> resMap = new HashMap<>();
        resMap.put("appId", appId);
        resMap.put("timeStamp", timeStamp);
        resMap.put("nonceStr", nonceStr);
        resMap.put("package", packageStr);
        // 使用字段appId、timeStamp、nonceStr、package进行签名
        String paySign = sign(appId, timeStamp, nonceStr, packageStr);
        resMap.put("paySign", paySign);
        resMap.put("signType", "SHA256withRSA");
        return resMap;
    }

    String sign(String appId, String timeStamp, String nonceStr, String packageStr) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, SignatureException {
        String content = appId + "\n"
                + timeStamp + "\n"
                + nonceStr + "\n"
                + packageStr + "\n";
        Signature sign = Signature.getInstance("SHA256withRSA");
        sign.initSign(wechatPayConfig.getPrivateKey(wechatPayConfig.getKeyPemPath()));
        sign.update(content.getBytes("utf-8"));
        return Base64.getEncoder().encodeToString(sign.sign());
    }

    private Map<String, Object> getNotifyBody(HttpServletRequest request) {
        //处理通知参数
        String body = HttpUtils.readData(request);
        log.info("支付回调参数：{}", body);
        // 转换为Map
        Map<String, Object> bodyMap = JSONObject.parseObject(body, new TypeReference<Map<String, Object>>() {
        });
        // 微信的通知ID（通知的唯一ID）
        String notifyId = bodyMap.get("id").toString();
        // 验证签名信息
        WechatPayValidator wechatPayValidator
                = new WechatPayValidator(verifier, notifyId, body);
        if (!wechatPayValidator.validate(request)) {
            log.error("通知验签失败");
            return null;
        }
        log.info("通知验签成功");
        return bodyMap;
    }

    private Map<String, String> falseMsg(HttpServletResponse response) {
        Map<String, String> resMap = new HashMap<>(8);
        //失败应答
        response.setStatus(500);
        resMap.put("code", "ERROR");
        resMap.put("message", "通知验签失败");
        return resMap;
    }

    private Map<String, String> trueMsg(HttpServletResponse response) {
        Map<String, String> resMap = new HashMap<>(8);
        //成功应答
        response.setStatus(200);
        resMap.put("code", "SUCCESS");
        resMap.put("message", "成功");
        return resMap;
    }

}
